//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================

#include <bsp.h>
#include "16550A.h"

EXTERN_C bool_t CDECL BspSetupComPort(
    ComPort comPort, const ComPortSettings *pSettings)
{
    if (!COMPORT_AVAILABLE(comPort)) return FALSE;

    ComPortSettings defaultSettings = {
        115200,
        ComPortByteSize_Default,
        ComPortParity_Default,
        ComPortStopBits_Default
    };

    if (NULL == pSettings) pSettings = &defaultSettings;

    // make line parameters
    uint8_t u8LineParams = 0;

    switch (pSettings->byteSize) {
        case ComPortByteSize_5:     u8LineParams |= LCR_WL_5;   break;
        case ComPortByteSize_6:     u8LineParams |= LCR_WL_6;   break;
        case ComPortByteSize_7:     u8LineParams |= LCR_WL_7;   break;
        case ComPortByteSize_8:     u8LineParams |= LCR_WL_8;   break;
        default: return FALSE;
    }

    switch (pSettings->stopBits) {
        case ComPortStopBits_One:   u8LineParams |= LCR_SB_1;   break;
        case ComPortStopBits_One5:  u8LineParams |= LCR_SB_1_5; break;
        case ComPortStopBits_Two:   u8LineParams |= LCR_SB_2;   break;
        default: return FALSE;
    }

    switch (pSettings->parity) {
        case ComPortParity_No:      u8LineParams |= LCR_NP;     break;
        case ComPortParity_Odd:     u8LineParams |= LCR_OP;     break;
        case ComPortParity_Even:    u8LineParams |= LCR_EP;     break;
        case ComPortParity_Mark:    u8LineParams |= LCR_HP;     break;
        case ComPortParity_Space:   u8LineParams |= LCR_LP;     break;
        default: return FALSE;
    }

    ioport_t portBase = COMPORT_BASE(comPort);

    // Disable uart interrupt
    Outb(COMPORT_IER(portBase), 0);

    //looking LSR to ensure both holder register and shift register are empty
    uint8_t u8LSR;
    while (1) {
        u8LSR = Inb(COMPORT_LSR(portBase));
        if ((u8LSR & LSR_EDHR) != 0) break;
    }

    // Set baut rate
    uint16_t u16Divisor = (uint16_t)(COMPORT_CLOCKRATE / pSettings->uBaudRate);

    uint8_t u8LCR = Inb(COMPORT_LCR(portBase));
    u8LCR |= LCR_DLAB;
    Outb(COMPORT_LCR(portBase), u8LCR);

    Outb(COMPORT_DLLB(portBase), LOBYTE(u16Divisor));
    Outb(COMPORT_DLHB(portBase), HIBYTE(u16Divisor));

    // Set line parameters
    Outb(COMPORT_LCR(portBase), u8LineParams);

    // Enable FIFO
    Outb(COMPORT_FCR(portBase), FCR_EF | FCR_CRXF | FCR_CTXF);

    return TRUE;
}

EXTERN_C bool_t CDECL BspWriteComPort(ComPort comPort, uint8_t u8Data)
{
    if (!COMPORT_AVAILABLE(comPort)) return FALSE;

    ioport_t portBase = COMPORT_BASE(comPort);

    uint8_t u8LSR;

    while (TRUE) {
        u8LSR = Inb(COMPORT_LSR(portBase));
        if (u8LSR & LSR_ETHR) break;
    }

    Outb(COMPORT_DAT(portBase), u8Data);

    return TRUE;
}

EXTERN_C bool_t CDECL BspReadComPort(ComPort comPort, uint8_t *pu8Data)
{
    if (NULL == pu8Data) return FALSE;

    if (!COMPORT_AVAILABLE(comPort)) return FALSE;

    ioport_t portBase = COMPORT_BASE(comPort);

    uint8_t u8LSR;

    while (TRUE) {
        u8LSR = Inb(COMPORT_LSR(portBase));
        if (u8LSR & LSR_DR) break;
    }

    // Has errors ?
    if (u8LSR & LSR_ERRS) return FALSE;

    *pu8Data = Inb(COMPORT_DAT(portBase));

    return TRUE;
}
